<?php
/**
 * Represents a table in the database, allowing basic table operations and
 * storing table-specific properties.
 * 
 * @package Bedrock
 * @author Nick Williams
 * @version 1.0.0
 * @created 08/29/2008
 * @updated 08/29/2008
 */
class Bedrock_Model_Table extends Bedrock_Model {
	const STATE_UNCHANGED = 0;
	const STATE_CHANGED = 1;
	const STATE_NEW = 2;
	
	const FORMAT_TYPE_SQL = 0;
	const FORMAT_TYPE_XML = 1;
	const FORMAT_TYPE_YAML = 2;
	const FORMAT_TYPE_CSV = 3;
	
	const TYPE_TABLE = 0;
	const TYPE_MAP = 1;
	const TYPE_VIEW = 2;
	
	const MAP_TYPE_ONE_ONE = 0;
	const MAP_TYPE_ONE_MANY = 1;
	const MAP_TYPE_MANY_ONE = 2;
	const MAP_TYPE_MANY_MANY = 3;
	
	protected $_name;
	protected $_type;
	protected $_mappings;
	protected $_columns;
	protected $_columns_add;
	protected $_columns_drop;
	protected $_columns_alter;
	protected $_columns_insert;
	protected $_indexes;
	protected $_key_primary;
	protected $_keys_foreign;
	protected $_properties;
	protected $_defaults;
	protected $_state;
	
	/**
	 * Initializes the table object.
	 * 
	 * @param array $tableConfig the table configuration
	 * @param Bedrock_Model_Database $database the Database object to use
	 */
	public function __construct($tableConfig, $database = NULL) {
		Bedrock_Common_Logger::logEntry();
		
		try {
			parent::__construct($database);
			
			$this->_name = $tableConfig['name'];
			$this->_properties = $tableConfig['properties'];
			// @todo handle column details in tableConfig
			$this->_state = self::STATE_NEW;
		}
		catch(PDOException $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('The table object could not be initialized.');
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('The table object could not be initialized.');
		}
	}
	
	/**
	 * Loads the table schema from the database.
	 */
	public function load() {
		Bedrock_Common_Logger::logEntry();
		
		try {
			$resTable = $this->_connection->query('SHOW TABLE STATUS WHERE Name = \'' . addslashes($this->_name) . '\'');
			$resColumns = $this->_connection->query('SHOW FULL COLUMNS FROM ' . addslashes($this->_name));
			
			// Retrieve Table Info
			$this->_properties = array();
			$rowTable = $resTable->fetch(PDO::FETCH_ASSOC);
			
			$this->_properties['engine'] = $rowTable['Engine'];
			$this->_properties['length'] = $rowTable['Data_length'];
			$this->_properties['rows'] = $rowTable['Rows'];
			$this->_properties['auto_increment'] = $rowTable['Auto_increment'];
			$this->_properties['collation'] = $rowTable['Collation'];
			$this->_properties['created'] = $rowTable['Create_time'];
			$this->_properties['updated'] = $rowTable['Update_time'];
			$this->_properties['comment'] = $rowTable['Comment'];
			
			if($rowTable['Comment'] != '' && substr($rowTable['Comment'], 0, 3) == 'map') {
				$this->_type = self::TYPE_MAP;
			}
			elseif($rowTable['Comment'] == 'VIEW') {
				$this->_type = self::TYPE_VIEW;
			}
			else {
				$this->_type = self::TYPE_TABLE;
				
				// Determine Mappings
				$mappings = explode('|', $rowTable['Comment']);
				$mappings = explode(':', $mappings[1]);
				$mappings = explode(',', $mappings[1]);
				
				foreach($mappings as $mapping) {
					$table = substr($mapping, 0, strpos($mapping, '('));
					$matches = array();
					preg_match('#\((.*?)\)#', $mapping, $matches);
					
					Bedrock_Common_Logger::info('Mapping with table "' . $table . '" found: ' . $matches[1]);
					
					switch($matches[1]) {
						case 'one_one':
							$type = self::MAP_TYPE_ONE_ONE;
							break;
							
						default:
						case 'one_many':
							$type = self::MAP_TYPE_ONE_MANY;
							break;
							
						case 'many_one':
							$type = self::MAP_TYPE_MANY_ONE;
							break;
							
						case 'many_many':
							$type = self::MAP_TYPE_MANY_MANY;
							break;
					}
					
					$this->_mappings[$table] = $type;
				}
			}
			
			// Retrieve Column Info
			$this->_columns = array();
			$rows = $resColumns->fetchAll(PDO::FETCH_ASSOC);
			
			foreach($rows as $row) {
				$columnData = array();
				$type = substr($row['Type'], 0, strpos($row['Type'], '('));
				
				if(!$type) {
					$type = $row['Type'];
				}
				
				$columnData = array('name' => $row['Field'],
								'type' => $type,
								'size' => substr($row['Type'], strpos($row['Type'], '(') + 1, strlen($row['type']) - 1),
								'null' => ($row['Null'] != 'NO'),
								'default' => $row['Default'],
								'primary_key' => ($row['Key'] == 'PRI'));
				
				// Set Foreign Key Type (if any)
				if($row['Comment'] != '' && substr($row['Comment'], 0, 2) == 'fk') {
					$columnData['foreign_key'] = substr($row['Comment'], 3);
					
					switch($this->_mappings[$columnData['foreign_key']]) {
						case self::MAP_TYPE_ONE_ONE:
							$columnData['foreign_key_type'] = Bedrock_Model_Column::FK_TYPE_ONE_TO_ONE;
							break;
							
						case self::MAP_TYPE_ONE_MANY:
							$columnData['foreign_key_type'] = Bedrock_Model_Column::FK_TYPE_ONE_TO_MANY;
							break;
							
						case self::MAP_TYPE_MANY_ONE:
							$columnData['foreign_key_type'] = Bedrock_Model_Column::FK_TYPE_MANY_TO_ONE;
							break;
							
						case self::MAP_TYPE_MANY_MANY:
							$columnData['foreign_key_type'] = Bedrock_Model_Column::FK_TYPE_MANY_MANY;
							break;
							
						default:
							$columnData['foreign_key_type'] = Bedrock_Model_Column::FK_TYPE_NONE;
							break;
					}
				}
				else {
					$columnData['foreign_key_type'] = Bedrock_Model_Column::FK_TYPE_NONE;
				}
				
				$columnObj = new Bedrock_Model_Column($columnData);
				
				$this->_columns[] = $columnObj;
				
				if($columnData['primary_key']) {
					$this->_key_primary = $columnObj;
				}
				elseif($columnData['foreign_key']) {
					$this->_keys_foreign[$columnData['foreign_key']] = $columnObj;
				}
			}
			
			$this->_state = self::STATE_UNCHANGED;
		}
		catch(PDOException $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('There was a problem retrieving the table information.');
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('There was a problem retrieving the table information.');
		}
	}
	
	/**
	 * Saves the current table schema, creating a new table if it does not yet
	 * exist.
	 */
	public function save() {
		Bedrock_Common_Logger::logEntry();
		
		try {
			if($this->_state == self::STATE_CHANGED) {
				// Update Table
				$sql = 'ALTER TABLE ' . addslashes($this->_name);
				
				// Columns: Add
				if(is_array($this->_columns_add) && count($this->_columns_add) > 0) {
					foreach($this->_columns_add as $newColumn) {
						$sql .= ' ADD COLUMN ' . $newColumn->definition . ',';
					}
				}
				
				// Columns: Insert
				if(is_array($this->_columns_insert) && count($this->_columns_insert) > 0) {
					foreach($this->_columns_insert as $insertColumnData) {
						$insertColumn = $insertColumnData['column'];
						$after = $insertColumnData['after'];
						
						$sql .= ' ADD COLUMN ' . $insertColumn->definition . ' AFTER ' . $after . ',';
					}
				}
				
				// Columns: Alter
				if(is_array($this->_columns_alter) && count($this->_columns_alter) > 0) {
					foreach($this->_columns_alter as $alterColumn) {
						$sql .= ' MODIFY COLUMN ' . $alterColumn->definition . ' ';
					}
				}
				
				// Columns: Drop
				if(is_array($this->_columns_drop) && count($this->_columns_drop) > 0) {
					foreach($this->_columns_drop AS $dropColumn) {
						$sql .= ' DROP COLUMN ' . $dropColumn->name . ',';
					}
				}
				
				// Table Properties
				$sql .= ' ENGINE = ' . $this->_properties['engine'] .
							', DEFAULT CHARACTER SET ' . $this->_properties['charset'] .
							', DEFAULT COLLATE ' . $this->_properties['collate'] .
							', COMMENT = \'' . addslashes($this->getMappingString()) . '\'';
				
				// Execute Query
				$this->_connection->exec($sql);
			}
			elseif($this->_state == self::STATE_NEW) {
				// Create Table
				$sql = 'CREATE TABLE ' . addslashes($this->_name) . ' (';
				
				// Columns
				foreach($this->_columns as $column) {
					$sql .= $column->definition . ', ';
				}
				
				$sql = substr($sql, 0, strlen($sql)-2) . ')';
				
				// Table Properties
				if($this->_properties['engine']) {
					$sql .= ' ENGINE = ' . $this->_properties['engine'];
				}
				
				if($this->_properties['charset']) {
					$sql .= ' DEFAULT CHARACTER SET = ' . $this->_properties['charset'];
				}
				
				if($this->_properties['collation']) {
					$sql .= ' DEFAULT COLLATE = ' . $this->_properties['collation'];
				}
				
				if($this->_properties['comment']) {
					$sql .= ' COMMENT = \'' . addslashes($this->getMappingString()) . '\'';
				}
				
				// Execute Query
				$this->_connection->exec($sql);
			}
			
			$this->_state = self::STATE_UNCHANGED;
			
			Bedrock_Common_Logger::logExit();
		}
		catch(PDOException $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('A problem was encountered while saving the table schema.');
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('A problem was encountered while saving the table schema.');
		}
	}
	
	/**
	 * Drops the current table from the database.
	 */
	public function drop() {
		Bedrock_Common_Logger::logEntry();
		
		try {
			// Execute Query
			$this->_connection->query('DROP TABLE IF EXISTS ' . addslashes($this->_name));
			
			$this->_state = self::STATE_NEW;
			
			Bedrock_Common_Logger::logExit();
		}
		catch(PDOException $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('A problem was encountered while dropping the table from the database.');
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('A problem was encountered while dropping the table from the database.');
		}
	}
	
	/**
	 * Empties the table of all data and resets any auto increment fields.
	 */
	public function reset() {
		Bedrock_Common_Logger::logEntry();
		
		try {
			// Execute Query
			$this->_connection->query('DELETE FROM ' . addslashes($this->_name));
			$this->_connection->query('ALTER TABLE ' . addslashes($this->_name) . ' AUTO_INCREMENT = 0');
			
			Bedrock_Common_Logger::logExit();
		}
		catch(PDOException $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('A problem was encountered while dropping the table from the database.');
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('A problem was encountered while dropping the table from the database.');
		}
	}
	
	/**
	 * Returns an array of column objects for the current table.
	 *
	 * @return array an array of column objects
	 */
	public function getColumns() {
		return $this->_columns;
	}
	
	/**
	 * Adds a new column to the table.
	 *
	 * @param Bedrock_Model_Column $newColumn the column to add to the table
	 */
	public function addColumn($newColumn) {
		$this->_columns_add[] = $newColumn;
		
		if($newColumn->foreign_key) {
			$this->_mappings[$newColumn->foreign_key] = $this->mappingTypeFromString($newColumn->fkTypeToString());
			$this->_keys_foreign[$newColumn->foreign_key] = $newColumn;
		}
		
		$this->_state = self::STATE_CHANGED;
	}
	
	/**
	 * Inserts a new column after the specified column index.
	 *
	 * @param Bedrock_Model_Column $newColumn the column to add to the table
	 * @param integer $afterIndex the index after which to insert the column
	 */
	public function insertColumn($newColumn, $afterColumnName) {
		$this->_columns_insert[] = array('column' => $newColumn, 'after' => $afterColumnName);
		
		if($newColumn->foreign_key) {
			$this->_mappings[$newColumn->foreign_key] = $this->mappingTypeFromString($newColumn->fkTypeToString());
			$this->_keys_foreign[$newColumn->foreign_key] = $newColumn;
		}
		
		$this->_state = self::STATE_CHANGED;
	}
	
	/**
	 * Removes a column from the table.
	 * 
	 * @param string $columnName the name of the column to remove
	 */
	public function deleteColumn($columnName) {
		foreach($this->_columns as $key => $column) {
			if($column->name == $columnName) {
				$this->_columns_drop = $column;
				$this->_columns = array_diff_key($this->_columns, array($key => $column));
				
				if($column->foreign_key) {
					unset($this->_mappings[$column->foreign_key]);
					unset($this->_keys_foreign[$column->foreign_key]);
				}
				
				$this->_state = self::STATE_CHANGED;
				return;
			}
		}
	}
	
	/**
	 * Sets a custom property for the table (useful for properties not directly
	 * supported). If no value is specified, a default will be selected if
	 * available.
	 *
	 * @param string $propertyName the name of the property to set
	 * @param string $propertyValue the value for the property
	 */
	public function setProperty($propertyName, $propertyValue) {
		if($propertyValue == '') {
			$this->_properties[$propertyName] = $this->_defaults[$propertyName];
		}
		else {
			$this->_properties[$propertyName] = $propertyValue;
		}
		
		$this->_state = self::STATE_CHANGED;
	}
	
	/*
	 * Returns the requested property.
	 * 
	 * @param string $propertyName the name of the property to retrieve
	 * @return string the corresponding property value
	 */
	public function getProperty($propertyName) {
		// Setup
		$result = NULL;
		
		if($propertyName == 'name') {
			$result = $this->_name;
		}
		elseif($propertyName == 'type') {
			switch($this->_type) {
				case self::TYPE_TABLE:
					$result = 'TABLE';
					break;
					
				case self::TYPE_MAP:
					$result = 'MAP';
					break;
					
				case self::TYPE_VIEW:
					$result = 'VIEW';
					break;
					
				default:
					// Do Nothing
					break;
			}
		}
		elseif(array_key_exists($propertyName, $this->_properties)) {
			$result = $this->_properties[$propertyName];
		}
		
		return $result;
	}
	
	/**
	 * Retrieves the table's primary key.
	 *
	 * @return Bedrock_Model_Column the primary key's Column object
	 */
	public function getPrimaryKey() {
		return $this->_key_primary;
	}
	
	/**
	 * Retrieves all foreign key columns in the table.
	 *
	 * @return array an array of all corresponding foreign key Column objects
	 */
	public function getForeignKeys() {
		return $this->_keys_foreign;
	}
	
	/**
	 * Returns the currently set table mappings.
	 *
	 * @return array an array containing the current table mappings
	 */
	public function getMappings() {
		return $this->_mappings;
	}
	
	/**
	 * Writes the specified data to the specified location.
	 *
	 * @param string $fileLocation the location to write to
	 * @param string $fileContents the data to write to the file
	 */
	protected function writeFile($fileLocation, $fileContents) {
		if(is_file($fileLocation)) {
			unlink($fileLocation);
		}
		
		$fileHandle = fopen($fileLocation, 'w');
		fwrite($fileHandle, $fileContents);
		fclose($fileHandle);
	}
	
	/**
	 * Retrieves the mapping type based on the specified string.
	 *
	 * @param string $mappingTypeString the mapping type as a string
	 * @return integer the corresponding mapping type
	 */
	public function mappingTypeFromString($mappingTypeString) {
		// Setup
		$mappingType = NULL;
		
		switch($mappingTypeString) {
			case 'one_one':
				$mappingType = self::MAP_TYPE_ONE_ONE;
				break;
				
			case 'one_many':
				$mappingType = self::MAP_TYPE_ONE_MANY;
				break;
				
			case 'many_one':
				$mappingType = self::MAP_TYPE_MANY_ONE;
				break;
				
			case 'many_many':
				$mappingType = self::MAP_TYPE_MANY_MANY;
				break;
		}
		
		return $mappingType;
	}
	
	/**
	 * Builds the current table's mapping string based on its properties.
	 * 
	 * @return string the resulting mapping string
	 */
	public function getMappingString() {
		// Setup
		$mappingString = '';
		
		// Build Mapping String
		if($this->_type == self::TYPE_TABLE) {
			$mappingString = 'table';
		}
		else {
			$mappingString = 'map';
		}
		
		if(count($this->_mappings)) {
			$mappingString .= '|mappings:';
		}
		
		foreach($this->_mappings as $tableName => $mappingType) {
			$mappingString .= $tableName . '(';
			
			switch($mappingType) {
				case self::MAP_TYPE_ONE_ONE:
					$mappingString .= 'one_one';
					break;
					
				case self::MAP_TYPE_ONE_MANY:
					$mappingString .= 'one_many';
					break;
					
				case self::MAP_TYPE_MANY_ONE:
					$mappingString .= 'many_one';
					break;
					
				case self::MAP_TYPE_MANY_MANY:
					$mappingString .= 'many_many';
					break;
			}
			
			$mappingString .= '),';
		}
		
		$mappingString = substr($mappingString, 0, strlen($mappingString)-1);
		
		return $mappingString;
	}
	
	/**
	 * Returns the specified format type extension as a string.
	 *
	 * @param integer $format the format type to translate
	 * @return string the corresponding format extension
	 */
	public function formatToString($format) {
		// Setup
		$result = '';
		
		switch($format) {
			default:
				$result = '';
				break;
				
			case self::FORMAT_TYPE_SQL:
				$result = 'sql';
				break;
				
			case self::FORMAT_TYPE_XML:
				$result = 'xml';
				break;
				
			case self::FORMAT_TYPE_YAML:
				$result = 'yaml';
				break;
				
			case self::FORMAT_TYPE_CSV:
				$result = 'csv';
				break;
		}
		
		return $result;
	}
	
	/**
	 * Returns the current table's schema as a string of the specified type.
	 *
	 * @param integer $formatType the format type to use
	 * @return string the schema represented as a string
	 */
	public function schemaToString($formatType = self::FORMAT_TYPE_SQL) {
		Bedrock_Common_Logger::logEntry();
		
		try {
			// Setup
			$result = '';
			
			switch($formatType) {
				default:
					
				// SQL Query
				case self::FORMAT_TYPE_SQL:
					$result = 'CREATE TABLE ' . addslashes($this->_name) . ' (';
					
					// Columns
					foreach($this->_columns as $column) {
						$result .= $column->definition . ', ';
					}
					
					$result = substr($result, 0, strlen($result)-2) . ')';
					
					// Table Properties
					if($this->_properties['engine'] != '') {
						$result .= ' ENGINE = ' . $this->_properties['engine'];
					}
					
					if($this->_properties['charset'] != '') {
						$result .= ' DEFAULT CHARACTER SET = ' . $this->_properties['charset'];
					}
					
					if($this->_properties['collation'] != '') {
						$result .= ' DEFAULT COLLATE = ' . $this->_properties['collation'];
					}
					
					if($this->_properties['comment'] != '') {
						$result .= ' COMMENT = \'' . addslashes($this->_properties['comment']) . '\'';
					}
					
					break;
					
				// XML String
				case self::FORMAT_TYPE_XML:
					// @todo: Implement schema => XML
					break;
					
				// YAML String
				case self::FORMAT_TYPE_YAML:
					// @todo: Implement schema => YAML
					break;
					
				// CSV String
				case self::FORMAT_TYPE_CSV:
					// @todo: Implement schema => CSV
					break;
			}
			
			Bedrock_Common_Logger::logExit();
			return $result;
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('A problem was encountered converting the schema for table "' . $this->_name . '" to a string.');
		}
	}
	
	/**
	 * Returns the current table's data as a string of the specified type.
	 *
	 * @param integer $formatType the format type to use
	 * @return string the data represented as a string
	 */
	public function dataToString($formatType = self::FORMAT_TYPE_SQL) {
		Bedrock_Common_Logger::logEntry();
		
		try {
			// Setup
			$result = '';
			
			// Check for Unsaved Changes
			if($this->_state != self::STATE_UNCHANGED) {
				throw new Bedrock_Model_Exception('Table \'' . $this->_name . '\' data retrieval aborted, the table has unsaved changes.');
			}
			
			// Query Database
			$res = $this->_connection->query('SELECT * FROM ' . addslashes($this->_name));
			$rows = $res->fetchAll(PDO::FETCH_ASSOC);
			
			switch($formatType) {
				default:
					
				// SQL Queries
				case self::FORMAT_TYPE_SQL:
					foreach($rows as $row) {
						// Build SQL Query
						$insertQuery = 'INSERT INTO ' . addslashes($this->_name) . ' (';
						
						foreach($this->_columns as $column) {
							$insertQuery .= $column->name . ', ';
						}
						
						$insertQuery = substr($insertQuery, 0, strlen($insertQuery) - 2) . ') VALUES (';
						
						foreach($this->_columns as $column) {
							if($column->type == Bedrock_Model_Column::FIELD_TYPE_INT ||
									$column->type == Bedrock_Model_Column::FIELD_TYPE_FLOAT ||
									$column->type == Bedrock_Model_Column::FIELD_TYPE_DOUBLE ||
									$column->type == Bedrock_Model_Column::FIELD_TYPE_DECIMAL ||
									$column->type == Bedrock_Model_Column::FIELD_TYPE_BOOL) {
								$insertQuery .= $row[$column->name] . ', ';
							}
							else {
								$insertQuery .= '\'' . $row[$column->name] . '\', ';
							}
						}
						
						$insertQuery = substr($insertQuery, 0, strlen($insertQuery) -2) . ');';
						
						$result .= $insertQuery . "\n";
					}
					
					break;
					
				// XML Document
				case self::FORMAT_TYPE_XML:
					$xml = new SimpleXMLElement('<records></records>');
					
					foreach($rows as $row) {
						$rowXml = $xml->addChild('record');
						
						foreach($this->_columns as $column) {
							$rowXml->addChild($column->name, $row[$column->name]);
						}
					}
					
					$xml = dom_import_simplexml($xml)->ownerDocument;
					$xml->formatOutput = true;
					$result = $xml->saveXML();
					
					break;
					
				// YAML Document
				case self::FORMAT_TYPE_YAML:
					$yaml = '---' . "\n";
					$yaml .= 'records:' . "\n";
					
					foreach($rows as $row) {
						$rowYaml = '';
						
						foreach($this->_columns as $column) {
							$rowYaml .= '    ' . $column->name . ': ' . $row[$column->name] . "\n";
						}
						
						$rowYaml = '  - ' . substr($rowYaml, 4) . '    ' . "\n";
						$yaml .= $rowYaml;
					}
					
					$yaml = substr($yaml, 0, strlen($yaml) - 5) . '...';
					$result = $yaml;
					
					break;
					
				// CSV Document
				case self::FORMAT_TYPE_CSV:
					$csv = '';
					
					foreach($this->_columns as $column) {
						$csv .= $column->name . ',';
					}
					
					$csv = substr($csv, 0, strlen($csv) - 2) . "\n";
					
					foreach($rows as $row) {
						$rowCsv = '';
						
						foreach($this->_columns as $column) {
							$rowCsv .= $row[$column->name] . ',';
						}
						
						$csv .= substr($rowCsv, 0, strlen($rowCsv) - 2) . "\n";
					}
					
					$result = $csv;
					
					break;
			}
			
			Bedrock_Common_Logger::logExit();
			return $result;
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('A problem was encountered converting the data for table "' . $this->_name . '" to a string.');
		}
	}
	
	/**
	 * Imports the table schema using the specified source. The table will not
	 * be created until the Bedrock_Model_Table::save() method is called.
	 *
	 * @param string $importSource the source file to import from
	 * @param integer $importType the format of the source file
	 */
	public function importSchema($importSource, $importType = self::FORMAT_TYPE_SQL) {
		Bedrock_Common_Logger::logEntry();
		
		try {
			// @todo implement method
			
			Bedrock_Common_Logger::logExit();
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('Schema import failed.');
		}
	}
	
	/**
	 * Exports the table schema to the specified location using the specified
	 * format.
	 *
	 * @param string $exportLocation the file to which the exported schema will be saved
	 * @param integer $exportType the export format to use
	 */
	public function exportSchema($exportLocation, $exportType = self::FORMAT_TYPE_SQL) {
		Bedrock_Common_Logger::logEntry();
		
		try {
			Bedrock_Common_Logger::info('Exporting schema of table "' . $this->_name . '" as ' . strtoupper(self::formatToString($exportType)) . ' to "' . $exportLocation . '" ...');
			$fileContents = $this->schemaToString($exportType);
			self::writeFile($exportLocation, $fileContents);
			
			Bedrock_Common_Logger::logExit();
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('Schema export failed.');
		}
	}
	
	/**
	 * Imports table data from the specified source. The contents of the source
	 * file must match the table's current schema.
	 *
	 * @param string $importSource the source file to import from
	 * @param integer $importType the format of the source file
	 * @param boolean $append whether or not existing data should be replaced or appended to
	 */
	public function importData($importSource, $importType = self::FORMAT_TYPE_SQL, $append = false) {
		Bedrock_Common_Logger::logEntry();
		
		try {
			// Setup
			$importSql = array();
			
			switch($importType) {
				default:
				
				// SQL Query
				case self::FORMAT_TYPE_SQL:
					$importSql = file($importSource);
					break;
					
				// XML Document
				case self::FORMAT_TYPE_XML:
					$importXml = new SimpleXMLElement($importSource, NULL, true);
					
					foreach($importXml->record as $row) {
						$sql = 'INSERT INTO ' . $this->_name . '(';
						
						foreach($row->children() as $field) {
							$sql .= $field->getName() . ', ';
						}
						
						$sql = substr($sql, 0, strlen($sql) - 2) . ') VALUES (';
						
						foreach($row->children() as $field) {
							$sql .= '\'' . $field . '\', ';
						}
						
						$sql = substr($sql, 0, strlen($sql) -2) . ');';
						$importSql[] = $sql;
					}
					break;
					
				// YAML Document
				case self::FORMAT_TYPE_YAML:
					// @todo add support for YAML data import
					break;
					
				// CSV Document
				case self::FORMAT_TYPE_CSV:
					$importCsv = file($importSource);
					$columns = explode(',', array_shift($importCsv));
					$values = array();
					
					foreach($importCsv as $csv) {
						$values = explode(',', $csv);
						$sql = 'INSERT INTO ' . $this->_name . '(';
						
						foreach($columns as $key => $column) {
							if($key == count($columns) - 1) {
								$column = substr($column, 0, strlen($column) - 1);
							}
							
							$sql .= $column . ', ';
						}
						
						$sql = substr($sql, 0, strlen($sql) - 2) . ') VALUES (';
						
						foreach($values as $key => $value) {
							if($key == count($values) - 1) {
								$value = substr($value, 0, strlen($value) - 1);
							}
							
							$sql .= '\'' . $value . '\', ';
						}
						
						$sql = substr($sql, 0, strlen($sql) - 2) . ');';
						$importSql[] = $sql;
					}
					
					break;
			}
			
			if(!$append) {
				$this->_connection->exec('DELETE FROM ' . $this->_name);
			}
			
			foreach($importSql as $query) {
				$this->_connection->exec($query);
			}
			
			Bedrock_Common_Logger::logExit();
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('Data import failed.');
		}
	}
	
	/**
	 * Exports the table's data to the specified location using the specified
	 * format.
	 *
	 * @param string $exportLocation the file to which exported data will be saved
	 * @param integer $exportType the export format to use
	 */
	public function exportData($exportLocation, $exportType = self::FORMAT_TYPE_SQL) {
		Bedrock_Common_Logger::logEntry();
		
		try {
			Bedrock_Common_Logger::info('Exporting data in table "' . $this->_name . '" as ' . strtoupper(self::formatToString($exportType)) . '...');
			$fileContents = $this->dataToString($exportType);
			self::writeFIle($exportLocation, $fileContents);
			
			Bedrock_Common_Logger::logExit();
		}
		catch(Exception $ex) {
			Bedrock_Common_Logger::exception($ex);
			Bedrock_Common_Logger::logExit();
			throw new Bedrock_Model_Exception('Data export failed.');
		}
	}
}
?>